package cc.shacocloud.mirage.bean.impl;

import cc.shacocloud.mirage.bean.FactoryPointHandler;
import cc.shacocloud.mirage.bean.QualifierHandler;
import cc.shacocloud.mirage.bean.ScopeHandler;
import cc.shacocloud.mirage.bean.bind.Bean;
import cc.shacocloud.mirage.bean.meta.BeanKey;
import cc.shacocloud.mirage.bean.meta.FactoryPoint;
import cc.shacocloud.mirage.bean.meta.FactoryPointMethod;
import cc.shacocloud.mirage.utils.ResolvableType;
import cc.shacocloud.mirage.utils.Utils;
import cc.shacocloud.mirage.utils.annotation.AnnotatedElementUtils;
import cc.shacocloud.mirage.utils.reflection.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * {@link FactoryPointHandler} 的注解实现
 *
 * @author 思追(shaco)
 */
@Slf4j
public class AnnotationFactoryPointHandler extends AbstractBeanHandler implements FactoryPointHandler {
    
    public AnnotationFactoryPointHandler(@NotNull QualifierHandler qualifierHandler,
                                         @NotNull ScopeHandler scopeHandler) {
        super(qualifierHandler, scopeHandler);
    }
    
    @NotNull
    @Override
    public List<FactoryPoint> getFactoryPoints(@NotNull BeanKey factoryBean) {
        Class<?> beanClass = factoryBean.getBeanClass();
        Method[] allMethods = ReflectUtil.getMethods(beanClass);
        
        List<FactoryPoint> factoryPoints = new ArrayList<>();
        
        for (Method method : allMethods) {
            if (methodIsFactoryPoint(method) && !ReflectUtil.isOverridden(method, beanClass)) {
                
                checkInjectionPoint(method);
                
                FactoryPointMethod factoryPointMethod = createFactoryPointMethod(method, factoryBean);
                factoryPoints.add(factoryPointMethod);
            }
        }
        
        return factoryPoints;
    }
    
    /**
     * 创建 {@link FactoryPointMethod}
     */
    protected FactoryPointMethod createFactoryPointMethod(@NotNull Method method,
                                                          @NotNull BeanKey factoryBean) {
        List<BeanKey> parameters = getExecutableParameters(method);
        String qualifier = qualifierHandler.getQualifier(method);
        
        ResolvableType methodReturnType = ResolvableType.forMethodReturnType(method);
        BeanKey returnType = new BeanKey(methodReturnType, qualifier);
        
        boolean singleton = scopeHandler.isSingleton(method);
        Bean bean = AnnotatedElementUtils.getAnnotation(method, BEAN_ANNOTATION);
        
        return new FactoryPointMethod(factoryBean, method, returnType, parameters, singleton, Objects.isNull(bean) || bean.lazy());
    }
    
    /**
     * 判断方法是否是工厂的注入点方法
     */
    protected boolean methodIsFactoryPoint(@NotNull Method method) {
        return !ReflectUtil.isStatic(method) && AnnotatedElementUtils.hasAnnotation(method, BEAN_ANNOTATION);
    }
    
    /**
     * 检查是否是注入点
     */
    protected void checkInjectionPoint(@NotNull Method method) {
        if (method.isAnnotationPresent(INJECTION_ANNOTATION)) {
            throw new UnsupportedOperationException(String.format("工厂方法 %s 上不能同时出现 @Inject 和 @Bean，即不能同时是工厂方法点和注入点！",
                    Utils.methodDescription(method)));
        }
    }
}
